package com.canyou.datamask;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

/**
 * 脱敏插件
 *
 * @author fy
 * @date 20/12/07 10:59
 */
@Slf4j
@Component
@Intercepts(@Signature(type = ResultSetHandler.class,
        method = "handleResultSets",
        args = {Statement.class}))
public class SensitiveInterceptor implements Interceptor {

    @SuppressWarnings("unchecked")
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        List<Object> records = (List<Object>) invocation.proceed();
        final Map<String, Desensitizer> cacheFieldStrategy = new HashMap<>(10);
        boolean isInit = false;
        // 对结果集脱敏
        for (int i = 0; i < records.size(); i++) {
            Object object = records.get(i);
            this.sensitive(object, cacheFieldStrategy, isInit);
            if (i == 0) {
                isInit = true;
            }
        }
        return records;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    private void sensitive(Object source, Map<String, Desensitizer> cacheFieldStrategy, boolean isInit) {
        if (isInit && cacheFieldStrategy.size() > 0) {
            // 初始化返回值类型的 MetaObject
            MetaObject metaObject = SystemMetaObject.forObject(source);
            cacheFieldStrategy.entrySet().forEach(entry -> {
                Object value = metaObject.getValue(entry.getKey());
                if (value != null) {
                    Object o = entry.getValue().apply((String) value);
                    // 把脱敏后的值塞回去
                    metaObject.setValue(entry.getKey(), o);
                }
            });
        } else {
            // 拿到返回值类型
            Class<?> sourceClass = source.getClass();
            // 初始化返回值类型的 MetaObject
            MetaObject metaObject = SystemMetaObject.forObject(source);
            // 捕捉到属性上的标记注解 @Sensitive 并进行对应的脱敏处理
            Stream.of(sourceClass.getDeclaredFields())
                    .filter(field -> field.isAnnotationPresent(Sensitive.class))
                    .forEach(field -> doSensitive(metaObject, field, cacheFieldStrategy));
        }
    }

    private void doSensitive(MetaObject metaObject, Field field, Map<String, Desensitizer> cacheFieldStrategy) {
        // 拿到属性名
        String name = field.getName();
        // 获取属性值
        Object value = metaObject.getValue(name);
        // 只有字符串类型才能脱敏  而且不能为null
        if (String.class == metaObject.getGetterType(name) && value != null) {
            Sensitive annotation = field.getAnnotation(Sensitive.class);
            // 获取对应的脱敏策略 并进行脱敏
            SensitiveStrategy type = annotation.strategy();
            Desensitizer desensitizer = type.getDesensitizer();
            Object o = desensitizer.apply((String) value);
            // 把脱敏后的值塞回去
            metaObject.setValue(name, o);
            cacheFieldStrategy.put(name, desensitizer);
        }
    }
}
